﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using CKEditor.NET;
using ReadyApps.Common;
using ReadyApps.Common.Exceptions;
using ReadyApps.Data;
using ReadyApps.Data.Wiki;
using ReadyApps.Wiki.Templates;

namespace ReadyApps.Wiki.Views
{
    class EditView : ContentViewBase, IViewFiresTitleSetEvent
    {
        #region Inner Classes

        protected class TextBoxContainer
        {
            #region Properties

            public string Label { get; private set; }

            public TextBox Box { get; private set; }

            public Func<PageContent, string> BoxValue { get; private set; }

            #endregion

            #region Constructor

            public TextBoxContainer(string label, TextBox box, Func<PageContent, string> boxValue)
            {
                Label = label;
                Box = box;
                BoxValue = boxValue;
            }

            #endregion
        }

        #endregion

        #region Variables and Constants

        internal const string EDITOR_CONTROL_ID = "editorControl";
        internal const string PAGE_TITLE_CONTROL_ID = "txtPageTitle";

        protected string baseFolder;
        private PageDetails pageDetails;
        private IWikiSecurityManager securityManager;
        protected CKEditorControl editor;
        protected TextBox txtPageTitle;
        protected string headerText;

        #endregion

        #region Events

        private event EventHandler cancelButtonClick;
        public event EventHandler CancelButtonClick
        {
            add { cancelButtonClick += value; }
            remove { cancelButtonClick -= value; }
        }

        private event EventHandler<SavePageEventArgs> saveButtonClick;
        public event EventHandler<SavePageEventArgs> SaveButtonClick
        {
            add { saveButtonClick += value; }
            remove { saveButtonClick -= value; }
        }

        private event EventHandler<SavePageEventArgs> savePublishButtonClick;
        public event EventHandler<SavePageEventArgs> SavePublishButtonClick
        {
            add { savePublishButtonClick += value; }
            remove { savePublishButtonClick -= value; }
        }

        #endregion

        #region Constructor

        public EditView(int wikiPageID, string requestBaseFolder, PageDetails wikiPageDetails,
            WikiControlSettings wikiControlSettings, IWikiDataProvider wikiDataProvider, IWikiUrlManager wikiUrlManager,
            IWikiSecurityManager wikiSecurityManager)
            : base(wikiPageID, wikiControlSettings, wikiDataProvider, wikiUrlManager)
        {
            baseFolder = requestBaseFolder;
            pageDetails = wikiPageDetails;
            securityManager = wikiSecurityManager;
            headerText = "Edit Page";
        }

        #endregion

        #region Public Methods

        public override void BuildControlTree(ControlCollection controls, bool raiseEvents)
        {
            TraceManager.BeginMethod(TraceComponent.EditView, "BuildControlTree");

            Control container = new Control();

            // Instantiate the template
            ITemplate layoutTemplate = controlSettings.EditLayoutTemplate ?? new WikiDefaultEditLayoutTemplate();
            layoutTemplate.InstantiateIn(container);

            // Locate the content and wiki controls control
            Control contentControl = WebUtilities.FindControlRecursive(container, WikiController.CONTENT_CONTROL_ID);
            if (contentControl == null)
            {
                throw new MissingControlException(WikiController.CONTENT_CONTROL_ID, location: "edit template");
            }
            Control controlsControl = WebUtilities.FindControlRecursive(container, WikiController.CONTROLS_CONTROL_ID);
            if (controlsControl == null)
            {
                throw new MissingControlException(WikiController.CONTROLS_CONTROL_ID, location: "edit template");
            }

            // Lookup the page data
            PageContent pageData = GetPageData();

            // Set the header
            contentControl.Controls.Add(new HtmlGenericControl("h1") { InnerText = headerText });

            // Add the textbox fields
            foreach (var boxContainer in GetTextBoxContainers())
            {
                HtmlGenericControl outerDiv = new HtmlGenericControl("div");
                outerDiv.Controls.Add(new Literal() { Text = boxContainer.Label });
                HtmlGenericControl innerDiv = new HtmlGenericControl("div");
                innerDiv.Style.Add(HtmlTextWriterStyle.PaddingLeft, "20px");
                if (boxContainer.BoxValue != null)
                {
                    boxContainer.Box.Text = boxContainer.BoxValue(pageData);
                }
                innerDiv.Controls.Add(boxContainer.Box);
                outerDiv.Controls.Add(innerDiv);
                contentControl.Controls.Add(outerDiv);
            }

            editor = new CKEditorControl();
            editor.ID = EDITOR_CONTROL_ID;
            editor.BasePath = GetEditorBasePath(controlSettings);
            editor.Height = 600;

            editor.config.extraPlugins = "managedlink";
            //editor.config.extraPlugins = "autogrow,managedlink";
            editor.config.toolbar = new object[] {
                new object[] { "Source", "-", "Templates" },
                new object[] { "Cut", "Copy", "Paste", "PasteText", "PasteFromWord", "-", "Undo", "Redo" },
                new object[] { "Find", "Replace", "-", "SelectAll", "-", "SpellChecker", "Scayt" },
                "/",
                new object[] { "Bold", "Italic", "Underline", "Strike", "Subscript", "Superscript", "-", "RemoveFormat" },
                new object[] { "NumberedList", "BulletedList", "-", "Outdent", "Indent", "-", "Blockquote", "CreateDiv",
                "-", "JustifyLeft", "JustifyCenter", "JustifyRight", "JustifyBlock" },
                new object[] { "Link", "ManagedLink", "Unlink", "Anchor" },
                new object[] { "Image", "Table", "HorizontalRule", "SpecialChar", "PageBreak" },
                "/",
                new object[] { "Styles", "Format", "Font", "FontSize" },
                new object[] { "TextColor", "BGColor" },
                new object[] { "Maximize", "ShowBlocks", "-", "About" }
            };
            editor.Text = GetEditableText(pageData);

            contentControl.Controls.Add(editor);

            // Display the wiki controls that the user has access to
            List<WikiControlButton> options = GetAvailableOptions();
            for (int i = 0; i < options.Count; i++)
            {
                if (i != 0)
                {
                    controlsControl.Controls.Add(new Literal() { Text = WikiController.BUTTON_SPACER });
                }
                controlsControl.Controls.Add(options[i].RootControl);
            }

            controls.Clear();
            controls.Add(container);

            if (raiseEvents)
            {
                controlSettings.OnTitleSet(this, new TitleSetEventArgs(GetPageTitle(pageData)));
                controlSettings.OnIncludeResource(this, new IncludeResourceEventArgs(IncludeType.Stylesheet, WikiController.MANAGED_LINK_CSS_FILE));
                controlSettings.OnIncludeResource(this, new IncludeResourceEventArgs(IncludeType.ScriptFile, WikiController.CKEDITOR_URL_REDIRECT_FILE));
                controlSettings.OnIncludeResource(this, new IncludeResourceEventArgs(IncludeType.ScriptBlock, GetAvailablePageLinksJSVariable()));
            }

            TraceManager.EndMethod(TraceComponent.EditView, "BuildControlTree");
        }

        #endregion

        #region Virtual Methods

        protected virtual List<TextBoxContainer> GetTextBoxContainers()
        {
            List<TextBoxContainer> boxes = new List<TextBoxContainer>();

            txtPageTitle = new TextBox();
            txtPageTitle.ID = PAGE_TITLE_CONTROL_ID;
            boxes.Add(new TextBoxContainer("Page Title", txtPageTitle, p => GetPageTitle(p)));

            return boxes;
        }

        protected virtual PageContent GetPageData()
        {
            if (pageDetails.Publishable && pageDetails.PendingRevision)
            {
                return dataProvider.GetPageContentForPending(pageID);
            }
            else
            {
                return dataProvider.GetPageContent(pageID);
            }
        }

        protected virtual string GetPageTitle(PageContent content)
        {
            return content.Title;
        }

        /// <summary>
        /// Gets the text that is editable in conformance with CKEditor and the custom CKEditor
        /// plug-in.
        /// </summary>
        /// <returns>A string of HTML text to be used by CKEditor for editing the content.</returns>
        protected virtual string GetEditableText(PageContent content)
        {
            Control hostControl = new Control();

            GetContentControls(content, hostControl, true);

            StringBuilder sb = new StringBuilder();
            hostControl.RenderControl(new HtmlTextWriter(new StringWriter(sb)));

            return sb.ToString();
        }

        protected virtual IEnumerable<AvailablePageLink> GetAvailablePageLinks()
        {
            return dataProvider.GetAvailablePageLinks(baseFolder, pageID, true);
        }

        protected virtual SavePageEventArgs GetSavePageEventArgs()
        {
            return new SavePageEventArgs(txtPageTitle.Text, editor.Text);
        }

        #endregion

        #region Private Methods

        private void SaveButton_Click(object sender, EventArgs e)
        {
            if (saveButtonClick != null)
            {
                saveButtonClick(this, GetSavePageEventArgs());
            }
        }

        private void SavePublishButton_Click(object sender, EventArgs e)
        {
            if (savePublishButtonClick != null)
            {
                savePublishButtonClick(this, GetSavePageEventArgs());
            }
        }

        private List<WikiControlButton> GetAvailableOptions()
        {
            List<WikiControlButton> options = new List<WikiControlButton>(3);

            WikiControlButton cancelButton = WikiControlButton.GetFromTemplate(controlSettings.ButtonTemplate);
            cancelButton.ButtonControl.Text = "Cancel";
            cancelButton.ButtonControl.Click += cancelButtonClick;
            options.Add(cancelButton);

            WikiControlButton saveButton = WikiControlButton.GetFromTemplate(controlSettings.ButtonTemplate);
            saveButton.ButtonControl.Text = "Save";
            saveButton.ButtonControl.Click += new EventHandler(SaveButton_Click);
            options.Add(saveButton);

            if (pageDetails.Publishable && securityManager.CanManagePublishing)
            {
                WikiControlButton savePublishButton = WikiControlButton.GetFromTemplate(controlSettings.ButtonTemplate);
                savePublishButton.ButtonControl.Text = "Save and Publish";
                savePublishButton.ButtonControl.Click += new EventHandler(SavePublishButton_Click);
                options.Add(savePublishButton);
            }

            return options;
        }
        
        /// <summary>
        /// Generates a string of JavaScript code that represents the available pages that can be
        /// linked to from the current page.
        /// </summary>
        /// <returns>A string of JavaScript code used by the CKEditor plug-in.</returns>
        private string GetAvailablePageLinksJSVariable()
        {
            StringBuilder builder = new StringBuilder("var pageLinkKeys =\n[");
            bool loopExecuted = false;
            foreach (var link in GetAvailablePageLinks())
            {
                loopExecuted = true;
                builder.Append("\n\t['");
                builder.Append(link.PageTitle);
                builder.Append("', '");
                builder.Append(link.PageID);
                builder.Append("'],");
            }
            if (loopExecuted)
            {
                builder.Remove(builder.Length - 1, 1);
            }
            builder.Append("\n];\n");
            return builder.ToString();
        }

        private static string GetEditorBasePath(WikiControlSettings settings)
        {
            string basePath;
            if (!string.IsNullOrEmpty(settings.CKEditorBasePath))
            {
                basePath = settings.CKEditorBasePath;
            }
            else if (!string.IsNullOrEmpty(ReadyAppsSettings.CKEditorBasePath))
            {
                basePath = ReadyAppsSettings.CKEditorBasePath;
            }
            else
            {
                basePath = string.Empty;
            }
            return basePath;
        }

        #endregion
    }
}
